/*
 * Copyright (C) 2019-2020 Alibaba Group Holding Limited
 */

#include <stdlib.h>
#include <aos/aos.h>
#include <yoc/mic.h>
#include <voice.h>
#include "mic_internal.h"

static const char *TAG = "mic_voice";

#define MIN(x, y) ((x) > (y) ? (y) : (x))

typedef struct {
    char    *data;
    int     capacity;
    int     len;
    int     offset;
} data_buffer_t;

mic_event_t mic_evt;
static int g_mic_rec_start;
static void _mic_rec_copy_data(int index, uint8_t *data, int size);
static int mic_adaptor_debug_control(mic_t *mic, int flag);
static aui_mic_debug_hook g_debug_hook;
static data_buffer_t g_kws_buf;

#define BACLFLOW_LEN 0//(1024 * 300)
typedef struct {
    int len;
    char data[BACLFLOW_LEN];
} backflow_t;

backflow_t backflow[3] = {0};
static void backflow_copy(int id, void *data, int len)
{
    backflow_t *b = &backflow[id];

    if (BACLFLOW_LEN == 0) {
        return;
    }

    if ((b->len + len) < BACLFLOW_LEN) {
        memcpy(b->data + b->len, data, len);
        b->len += len;
    }
}

static void backflow_reset(void)
{
    backflow[0].len = 0;
    backflow[1].len = 0;
    backflow[2].len = 0;
}

static void voice_event(void *priv, voice_evt_id_t evt_id, void *data, int len)
{
    char *p = data;
    int data_len = len;

    if (evt_id == VOICE_ASR_EVT) {
        evt_id = MIC_EVENT_SESSION_START;
    } else if (evt_id == VOICE_DATA_EVT) {
        voice_data_t *vdata = (voice_data_t *)data;

        p = vdata->data;
        data_len = vdata->len;

        if (vdata->type == VOICE_MIC_DATA) {
            evt_id = MIC_EVENT_PCM_DATA;
            backflow_copy(0, p, data_len);
            _mic_rec_copy_data(0, (uint8_t *)p, data_len);

            if (g_debug_hook) {
                g_debug_hook(evt_id, p, data_len);
            }
            
            return;
        } else if (vdata->type == VOICE_REF_DATA) {
            backflow_copy(1, p, data_len);
            _mic_rec_copy_data(1, (uint8_t *)p, data_len);
            return;
        } else if (vdata->type == VOICE_VAD_DATA) {
            backflow_copy(2, p, data_len);
            _mic_rec_copy_data(2, (uint8_t *)p, data_len);

            if (vdata->flag == VOICE_VADSTAT_ASR_FINI) {
                evt_id = MIC_EVENT_SESSION_STOP;
            } else if (vdata->len > 0) {
                evt_id = MIC_EVENT_PCM_DATA;
            } else if (vdata->flag == VOICE_VADSTAT_VOICE) {
                evt_id = MIC_EVENT_VAD;
            } else {
                return;
            }
        } else if (vdata->type == VOICE_KWS_DATA) {
            LOGD(TAG, "kws data recv len %d", vdata->len);
            evt_id = MIC_EVENT_KWS_DATA;

            if (g_kws_buf.capacity < vdata->len) {
                aos_free(g_kws_buf.data);
                g_kws_buf.data = aos_malloc_check(vdata->len);
                g_kws_buf.capacity = vdata->len;
            }

            memcpy(g_kws_buf.data, vdata->data, vdata->len);
            g_kws_buf.len = vdata->len;
            g_kws_buf.offset = 0;
        }
    } else if (evt_id == VOICE_SILENCE_EVT) {
        evt_id = MIC_EVENT_SESSION_STOP;
    } else {
        LOGE(TAG, "unkown event id");
        return;
    }

    mic_evt(priv, evt_id, p, data_len);
}

static int mic_adaptor_init(mic_t *mic, mic_event_t event)
{
    mic_evt = event;
    voice_t *v;

    v = voice_init(voice_event, mic);
    mic_set_privdata(v);

    return 0;
}

static int mic_adaptor_deinit(mic_t *mic)
{
    voice_t * v = (voice_t *)mic_get_privdata();

    voice_stop(v);
    voice_deinit(v);

    return 0;
}

static int mic_adaptor_set_hook(mic_t *mic, aui_mic_debug_hook hook)
{
    g_debug_hook = hook;

    return 0;
}

static int mic_adaptor_kws_control(mic_t *mic, int flag)
{
    // voice_t * v = (voice_t *)mic_get_privdata();

    // if (flag == 0) {
    //     yv_asr_disable(v);
    // } else {
    //     yv_kws_enable(v);
    // }

    return 0;
}

static int mic_adaptor_get_wwv_data(mic_t *mic, void *kws_data, int max_data_len)
{
    aos_check_param(kws_data && max_data_len > 0);
    int remain_len = g_kws_buf.len - g_kws_buf.offset;
    int cp_len = 0;

    if (remain_len > 0) {
        cp_len = MIN(remain_len, max_data_len);
        memcpy(kws_data, g_kws_buf.data + g_kws_buf.offset, cp_len);
        g_kws_buf.offset += cp_len;
    }

    return cp_len;
}

static int mic_adaptor_pcm_data_control(mic_t *mic, int flag)
{
    voice_t * v = (voice_t *)mic_get_privdata();

    if (flag == 1) {
        backflow_reset();
    }

    if (g_mic_rec_start) {

        return 0;
    }
    // voice_backflow_control(v, VOICE_AEC_DATA, flag == 0? 0 : 1);
    // voice_backflow_control(v, VOICE_REF_DATA, flag == 0? 0 : 1);
    voice_backflow_control(v, VOICE_MIC_DATA, flag == 0? 0 : 1);
    // voice_backflow_control(v, VOICE_VAD_DATA, flag == 0? 0 : 1);

    return 0;
}

static int mic_adaptor_pcm_aec_control(mic_t *mic, int flag)
{
    // voice_t * v = (voice_t *)mic_get_privdata();

    return 0;
}

static int mic_adaptor_set_param(mic_t *mic, mic_param_t *param)
{
    voice_adpator_param_t *p = (voice_adpator_param_t*)param->ext_param1;
    voice_t              * v = (voice_t *)mic_get_privdata();
    voice_pcm_param_t      pcm_p;
    voice_param_t v_p;

    pcm_p.access        = 1;
    pcm_p.channles      = param->channels;
    pcm_p.channles_sum  = param->channels;
    pcm_p.rate          = param->rate;
    pcm_p.sample_bits   = param->sample_bits;
    pcm_p.pcm_name      = p->pcm;
    voice_add_mic(v, &pcm_p);

    // pcm_p.pcm_name      = "pcmC2";
    // voice_add_ref(v, &pcm_p);

    v_p.cts_ms          = p->cts_ms;
    v_p.ipc_mode        = p->ipc_mode;
    v_p.ai_param        = p->ai_param;
    v_p.ai_param_len    = p->ai_param_len;
    voice_config(v, &v_p);

    voice_start(v);
    voice_backflow_control(v, VOICE_VAD_DATA, 1);
    voice_backflow_control(v, VOICE_KWS_DATA, 1);

    return 0;
}

static int mic_adaptor_get_param(mic_t *mic, mic_param_t *hw)
{
    // voice_t * v = (voice_t *)mic_get_privdata();

    return 0;
}

static int mic_adaptor_get_kw(mic_t *mic, int kwid, char **kw)
{
    aos_check_param(kw);

    voice_t * v = (voice_t *)mic_get_privdata();

    voice_get_kw(v, kwid, kw);
    return 0;
}

static void _mic_rec_copy_data(int index, uint8_t *data, int size)
{
    if (g_mic_rec_start) {
        mic_rec_copy_data(index, data, size);
    }
}

static void mic_adaptor_rec_start(mic_t *mic, const char *url, const char *save_name)
{
    if (g_mic_rec_start) {
        LOGW(TAG, "mic rec start yet, please stop before starting");
        return;
    }
    mic_rec_start(url, save_name);
    mic_adaptor_pcm_data_control(mic, 1);
    g_mic_rec_start = 1;
}

static void mic_adaptor_rec_stop(mic_t *mic)
{
    if (g_mic_rec_start) {
        g_mic_rec_start = 0;
        mic_adaptor_pcm_data_control(mic, 0);
        mic_rec_stop();
    }
}

/* update the wakeup voice sate to ai side to allow voice cut */
static int mic_adaptor_wkp_voice_state(mic_t *mic, int flag)
{
    voice_t * v = (voice_t *)mic_get_privdata();

    voice_wkvc_stat_send(v, flag);
    return 0;
}

/* update the wakeup voice sate to ai side to allow voice cut */
static int mic_adaptor_fake_wakeup(mic_t *mic, int flag)
{
    voice_t * v = (voice_t *)mic_get_privdata();

    voice_fake_wakeup_send(v);
    return 0;
}

static int mic_adaptor_debug_control(mic_t *mic, int flag)
{
    // int id1 = BACKFLOW_MIC_DATA | BACKFLOW_REF_DATA | BACKFLOW_AEC_DATA;
    // int id2 = BACKFLOW_VAD_DATA;

    // yv_debug_mode = flag;

    // if (yv_debug_mode == 2 || yv_debug_mode == 1) {
    //     backflow_control_rec(mic, id1, 1);
    //     backflow_control_rec(mic, id2, 1);
    // } else if (yv_debug_mode == 0) {
    //     backflow_control_rec(mic, id1, 0);
    //     backflow_control_rec(mic, id2, 0);
    // }

    return 0;
}

static int mic_adaptor_lpm_data(mic_t *mic, void *mic_data, int len)
{
    voice_t              * v = (voice_t *)mic_get_privdata();

    voice_lpm_data(v, mic_data, len);

    return 0;
}

static mic_ops_t voice_ops = {
    .init = mic_adaptor_init,
    .deinit = mic_adaptor_deinit,
    .kws_control = mic_adaptor_kws_control,
    .pcm_data_control = mic_adaptor_pcm_data_control,
    .pcm_aec_control = mic_adaptor_pcm_aec_control,
    .pcm_set_param = mic_adaptor_set_param,
    .pcm_get_param = mic_adaptor_get_param,
    .mic_rec_start = mic_adaptor_rec_start,
    .mic_rec_stop = mic_adaptor_rec_stop,
    .wakup_audio_stat = mic_adaptor_wkp_voice_state,
    .kws_wake = mic_adaptor_fake_wakeup,
    .debug_control    = mic_adaptor_debug_control,
    .set_dbghook = mic_adaptor_set_hook,
    .wwv_get_data = mic_adaptor_get_wwv_data,
    .get_kw = mic_adaptor_get_kw,
    .lpm_data = mic_adaptor_lpm_data
};

void voice_mic_register(void)
{
    mic_ops_register(&voice_ops);
}
